package Cantella::DJabberd::Plugin::RosterStorage;

use strict;
use warnings;

use base 'DJabberd::RosterStorage';

use DJabberd::Log;
use Digest;

our $logger = DJabberd::Log->get_logger;
sub log { $logger }

#this sucks, but it's necessary unless i figure out how to use Gearman
#like the people at Danga did... I just don't grok Danga::Socket
sub blocking { 1 }

#config variables
sub set_config_schema {
  my ($self, $schema) = @_;
  $self->{'dbic_schema'} = $schema;
}

sub set_config_dsn {
  my ($self, $dbdsn) = @_;
  $self->{'dbic_dbdsn'} = $dbdsn;
}

sub set_config_dbusername {
  my ($self, $dbusername) = @_;
  $self->{'dbic_dbusername'} = $dbusername;
}

sub set_config_dbpassword {
  my ($self, $dbpassword) = @_;
  $self->{'dbic_dbpassword'} = $dbpassword;
}

sub set_config_schemaclass {
  my ($self, $schema_class) = @_;
  $self->{dbic_schema_class} = $schema_class;
}

#i could probably move this to a common super class or  maybe
#subclass DJabberd to have a schema slot? I don't know.
sub finalize {
  my $self = shift;

  return if eval { $self->{dbic_schema}->isa('DBIx::Class::Schema') };

  $logger->fatal("dsn not provided")      unless $self->{dbic_dbdsn};
  $logger->fatal("username not provided") unless $self->{dbic_dbusername};
  $logger->fatal("password not provided") unless $self->{dbic_dbpassword};

  my $schema = $self->{dbic_schema_class}->connect(
                                                 $self->{dbic_dbdsn},
                                                 $self->{dbic_dbusername},
                                                 $self->{dbic_dbpassword}
                                                );
  #warn("Successfully logged in to the database") if $schema;
  $logger->fatal("Could not connect to the database") unless $schema;
  $self->{dbic_schema} = $schema;
}

sub get_jid {
  my ($self,$jid) = @_;
  my $rs = $self->{dbic_schema}->resultset('UserJid')->search
    ( 
     { node => $jid->node, 'domain.hostname' => $jid->domain },
     { join => ['domain'], },
    );
  if(my $jid = $rs->first){
    return $jid;
  }
  return;
}


#this should probably happen at user creation!
sub get_roster {
  my ($self, $cb, $jid) = @_;

  if( defined(my $db_jid = $self->get_jid($jid)) ){
    #OK so I know this seems contrived, but the reality is that most people
    #have more contacts than they have groups in their rosters, so it's more
    #efficient to run the queries in this order to avoid a lot of tiny queries
    my %contacts;
    for my $group( $db_jid->roster_groups->all ){
      for my $item ( $group->roster_items({}, {prefetch => ['contact'] }) ){
        my $contact = $contacts{$item->contact_id} ||= DJabberd::RosterItem->new
          (
           jid          => $item->contact->jid,
           name         => $item->name,
           subscription => DJabberd::Subscription->from_bitmask($item->subscription),
          );
        $contact->add_group( $group->name );
      }
    }

    my $roster = DJabberd::Roster->new;
    $roster->add($_) values %contacts;
    $cb->set_roster($roster);
  }
  $logger->warn("Can't fetch roster, user '$jid' not found");
  return $cb->error;
}


# I can't remember why the next two are different. fuck.
sub set_roster_item {
  my ($self, $cb, $jid, $ritem) = @_;
  my $owner = $self->get_jid($jid);
  my $contact = $self->get_jid($ritem->jid);
  my $roster_item = $self->{dbic_schema}->resultset('RosterItem')->update_or_create
    ({
      name => $ritem->name,
      owner => $owner,
      contact => $contact,
      subscription => $ritem->subscription->as_bitmask,
     });

  my %new_groups = map{ $_       => 1  } $ritem->groups;
  my %old_groups = map{ $_->name => $_ } $roster_item->roster_groups;
  my @add_groups = grep { !exists $old_groups{$_} } keys %new_groups;
  my @rem_groups = grep { !exists $new_groups{$_} } keys %old_groups;

  $roster_item->remove_from_roster_groups($old_groups{$_}) for @rem_groups;
  #this last one could probably be more efficient...
  foreach my $group_name ( @add_groups ){
    $roster_item->owner->roster_groups->find_or_create({name => $group_name})
      ->add_to_roster_items($roster_item);
  }

  return $cb->done($ritem);
}

sub addupdate_roster_item {
  my ($self, $cb, $jid, $ritem) = @_;
  my $owner = $self->get_jid($jid);
  my $contact = $self->get_jid($ritem->jid);
  my $roster_item = $self->{dbic_schema}->resultset('RosterItem')->update_or_create
    ({
      name => $ritem->name,
      owner => $owner,
      contact => $contact,
      subscription => $ritem->subscription->as_bitmask,
     });
  $roster_item->name($ritem->name);
  $roster_item->update;

  my %new_groups = map{ $_       => 1  } $ritem->groups;
  my %old_groups = map{ $_->name => $_ } $roster_item->roster_groups;
  my @add_groups = grep { !exists $old_groups{$_} } keys %new_groups;
  my @rem_groups = grep { !exists $new_groups{$_} } keys %old_groups;

  $roster_item->remove_from_roster_groups($old_groups{$_}) for @rem_groups;
  foreach my $group_name ( @add_groups ){
    $roster_item->owner->roster_groups->find_or_create({name => $group_name})
      ->add_to_roster_items($roster_item);
  }

  my $sub = DJabberd::Subscription->from_bitmask($roster_item->subscription);
  $ritem->set_subscription($sub);
  return $cb->done($ritem);
}

#delete this particular item
sub delete_roster_item {
  my ($self, $cb, $jid, $ritem) = @_;
  my $owner = $self->get_jid($jid);
  my $contact = $self->get_jid($ritem->jid);
  my $needle = { owner => $user, contact => $contact };
  #soft delete?
  $self->{dbic_schema}->resultset('RosterItem')->find($needle)->delete;
  return $cb->done;
}

sub load_roster_item {
  my ($self, $jid, $target_jid, $cb) = @_;
  my $owner = $self->get_jid($jid);
  my $contact = $self->get_jid($target_jid);
  my $needle = { user => $user, contact => $contact };
  my $rs = $self->{dbic_schema}->resultset('RosterItem');

  if( defined(my $roster_item = $rs->find($needle, {prefetch => ['contact']})) ){
    my $sub = DJabberd::Subscription->from_bitmask($roster_item->subscription);
    my $item = DJabberd::RosterItem->new
      (
       jid => $roster_item->contact->jid,
       name => $roster_item->name,
       subscription => $sub,
      );

    $item->add_group( $_->name ) for $roster_item->roster_groups;
    return $cb->set($item);
  }

  return $cb->set(undef);
}

#delete everything in the roster
sub wipe_roster {
  my ($self, $cb, $jid) = @_;

  if(defined( my $user = $self->get_jid($jid) )){
    $user->roster_items->delete_all;
    $user->roster_groups->delete_all;
    return $cb->done;
  }

  $logger->warn("Can't fetch roster, user '$user_jid' not found");
  return $cb->error;
}


1;
